package com.qixiaobao.async.impl;


import com.qixiaobao.async.AsyncService;
import com.qixiaobao.common.config.SupplyConfig;
import com.qixiaobao.common.utils.MybatisBatchUtils;
import com.qixiaobao.common.utils.StringUtils;
import com.qixiaobao.common.utils.file.FileUtils;
import com.qixiaobao.common.utils.uuid.IdUtils;
import com.qixiaobao.insure.domain.Insure;
import com.qixiaobao.insure.domain.MatchInsure;
import com.qixiaobao.insure.mapper.InsureMapper;
import com.qixiaobao.insure.mapper.MatchInsureMapper;
import com.qixiaobao.system.domain.FileInfo;
import com.qixiaobao.system.domain.Supply;
import com.qixiaobao.system.mapper.SupplyMapper;
import com.qixiaobao.system.service.IFileInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @ClassName AsyncServiceImpl
 * @Author yangxm
 * @Description 异步处理
 * @Date 2024/3/12 14:38
 */
@Slf4j
@Async("threadPoolTaskExecutor")
@Service
public class AsyncServiceImpl implements AsyncService {

    @Autowired
    private MatchInsureMapper matchInsureMapper;
    @Autowired
    private SupplyMapper supplyMapper;
    @Autowired
    private InsureMapper insureMapper;
    @Autowired
    private IFileInfoService fileInfoService;

    /**
     * @Description 匹配投保数据处理异步
     * @Author yangxm
     * @Date 2025-06-18 11:17:47
     * @Param [toInsureIdList]
     * @param originalFlag 原始保单文件标识 true
     * @param dayFlag 天保保单文件标识 true
     */
    @Override
    public void matchInsureHandle(List<Long> fromInsureIdList, Boolean originalFlag, Boolean dayFlag) {
        log.info("匹配投保数据异步处理开始，匹配后的保单id：{}, 原始保单文件标识: {}, 天保标识：{}", fromInsureIdList, originalFlag, dayFlag);
        long begin = System.currentTimeMillis();
        List<MatchInsure> matchInsureList;
        if (dayFlag){
            matchInsureList = matchInsureMapper.selectDayMatchInsureList(fromInsureIdList);
        }else {
            if (originalFlag){
                matchInsureList = matchInsureMapper.selectMatchOriginalInsureList(fromInsureIdList);
            }else {
                matchInsureList = matchInsureMapper.selectMatchInsureList(fromInsureIdList);
            }
        }
        //根据匹配前保单ID分组，查询匹配前保单ID对应的匹配后保单ID集合
        Map<Long, List<Long>> toInsureIdMap = matchInsureList.stream().collect(Collectors.groupingBy(MatchInsure::getFromInsureId, Collectors.mapping(MatchInsure::getToInsureId, Collectors.toList())));
        //根据供应商id分组，查询匹配后供应商简称顺序
        Map<Long, String> toSupplyShortNameMap = supplyMapper.selectMathcSupplyShortNameList().stream().collect(Collectors.toMap(Supply::getId, Supply::getMatchSupplyShortName));
        //匹配投保数据
        MatchInsure matchInsure;
        //匹配后供应商简称顺序
        String toSupplyShortName;
        //匹配后的保单ID集合
        List<Long> toInsureIdList;
        //需要更新的保单信息
        List<Insure> fromInsureList = new ArrayList<>();
        //循环处理
        for (int i = 0; i < matchInsureList.size(); i++) {
            matchInsure = matchInsureList.get(i);
            //匹配后的保单id集合
            toInsureIdList = toInsureIdMap.get(matchInsure.getFromInsureId());
            if (StringUtils.isEmpty(toInsureIdList)) {
                log.info("未查到匹配后的保单ID，匹配前保单ID:{}, 原始保单文件标识: {}, 天保标识：{}", matchInsure.getFromInsureId(), originalFlag, dayFlag);
                continue;
            }
            log.info("匹配投保数据处理, 匹配前保单ID：{}，匹配后保单ID：{}, 原始保单文件标识: {}, 天保标识：{}", matchInsure.getFromInsureId(), toInsureIdList, originalFlag, dayFlag);
            //匹配后供应商简称顺序
            toSupplyShortName = toSupplyShortNameMap.getOrDefault(matchInsure.getFromSupplyId(), "");
            if (toInsureIdList.size() > 1 && (StringUtils.isBlank(toSupplyShortName) || toSupplyShortName.split(",").length == 1)) {
                log.info("匹配投保数据处理，匹配后供应商简称顺序为空或只有一个供应商简称，匹配前保单ID:{}, 原始保单文件标识: {}, 天保标识：{}, 匹配前供应商ID:{}，匹配后供应商简称顺序：{}", matchInsure.getFromInsureId(), originalFlag, dayFlag, matchInsure.getFromSupplyId(), toSupplyShortName);
                continue;
            }
            if (toInsureIdList.size() == 1) {
                //匹配前保单和匹配后保单一对一
                oneToOneHandle(matchInsure, toInsureIdList, fromInsureList, originalFlag, dayFlag);
            } else {
                //匹配前保单和匹配后保单一对多
                try {
                    oneToManyHandle(matchInsure, toInsureIdList, toSupplyShortName, fromInsureList, originalFlag, dayFlag);
                } catch (IOException e) {
                    log.error("匹配投保数据处理，匹配前保单和匹配后保单一对多数据处理异常：{}", e);
                    throw new RuntimeException(e);
                }
            }
        }
        //批量更新匹配前的保单信息
        if (StringUtils.isNotEmpty(fromInsureList)) {
            log.info("匹配投保数据处理保存，数据量：{}", fromInsureList.size());
            MybatisBatchUtils.batchUpdateOrInsert(fromInsureList, InsureMapper.class, InsureMapper::updateMatchInsureInfo);
        }
        log.info("匹配投保数据处理结束，匹配后的保单id：{}，耗时：{}", fromInsureIdList, System.currentTimeMillis() - begin);
    }

    /**
     * @Description 匹配前保单和匹配后保单一对多  数据处理
     * @Author yangxm
     * @Date 2025/6/18 11:31
     * @param matchInsure   匹配投保数据
     * @param toInsureIdList    匹配后的保单id集合
     * @param toSupplyShortName 匹配后供应商简称顺序
     * @param fromInsureList    需要更新的保单信息
     * @param originalFlag  原始保单文件标识 true
     * @param dayFlag  保保单文件标识 true
     */
    private void oneToManyHandle(MatchInsure matchInsure, List<Long> toInsureIdList, String toSupplyShortName, List<Insure> fromInsureList, Boolean originalFlag, Boolean dayFlag) throws IOException {
        log.info("匹配投保数据一对多处理，匹配前保单ID：{}, 匹配后保单ID：{}, 原始保单文件标识: {}, 天保标识：{} ", matchInsure.getFromInsureId(), toInsureIdList, originalFlag, dayFlag);
        //根据匹配后的保单id集合，查询匹配后的保单信息，并以传入参数匹配后的供应商简称排序
        List<Insure> toInsureList = insureMapper.selectToInsureByToInsureIdList(toInsureIdList, StringUtils.str2List(toSupplyShortName, ",", true, true));
        if (StringUtils.isEmpty(toInsureList)){
            log.info("匹配投保数据一对多处理，未查到匹配后的保单信息，匹配前保单ID：{}, 匹配后保单ID：{}, 原始保单文件标识: {}", matchInsure.getFromInsureId(), toInsureIdList, originalFlag);
            return;
        }
        Insure fromInsure = new Insure();
        //匹配前保单ID
        fromInsure.setId(matchInsure.getFromInsureId());
        String insureFile;
        //原始保单文件处理
        if (originalFlag){
            List<String> originalInsureFileList = toInsureList.stream().map(Insure::getOriginalInsureFileUrl).filter(originalInsureFileUrl -> !Objects.equals(originalInsureFileUrl, "")).collect(Collectors.toList());
            if (StringUtils.isEmpty(originalInsureFileList) || originalInsureFileList.size() != toInsureList.size()){
                log.info("匹配投保数据处理，匹配后的原始保单文件缺失，匹配后的保单ID：{}，匹配后的原始保单文件：{}", toInsureIdList, originalInsureFileList);
                insureFile = "";
            }else {
                //文件封装
                insureFile = insureFileHandle(originalInsureFileList, true);
            }
            fromInsure.setOriginalInsureFileUrl(insureFile);
        }else {
            //匹配后的保单号集合
            List<String> insureNoList = toInsureList.stream().map(Insure::getInsureNo).filter(insureno -> !Objects.equals(insureno, "")).collect(Collectors.toList());
            //匹配后的保单号集合不为空，且保单号的数量和匹配后的保单数量一致
            if (StringUtils.isNotEmpty(insureNoList) && insureNoList.size() == toInsureList.size()){
                fromInsure.setInsureNo(StringUtils.join(insureNoList, ","));
            }else {
                fromInsure.setInsureNo("");
            }
            List<String> insureFileList = toInsureList.stream().map(Insure::getInsureFile).filter(insurefile -> !Objects.equals(insurefile, "")).collect(Collectors.toList());
            if (StringUtils.isEmpty(insureFileList) || insureFileList.size() != toInsureList.size()){
                log.info("匹配投保数据处理，匹配后的保单文件缺失，匹配后的保单ID：{}，匹配后的保单文件：{}", toInsureIdList, insureFileList);
                insureFile = "";
            }else {
                insureFile = insureFileHandle(insureFileList, false);
            }
            fromInsure.setInsureFile(insureFile);
        }
        fromInsureList.add(fromInsure);
    }

    /**
     * @Description 保单文件处理
     * @Author yangxm
     * @Date 2025/6/18 11:38
     * @param insureFileList
     * @param originalFlag
     * @return java.lang.String
     */
    private String insureFileHandle(List<String> insureFileList, Boolean originalFlag) throws IOException {

        String insureFile = null;
        //文件初始路径
        String basePath = SupplyConfig.getProfile() + FileUtils.getPath() + IdUtils.fastSimpleUUID();
        //合并前的下载的保单文件路径
        String mergeBeforePath = basePath + "/before/";
        //合并后的文件路径
        String mergeAfterPath = basePath + "/after/";
        //文件URL
        String fileUrl;
        //下载后的文件的全路径
        String filePath;
        //文件实体
        File file;
        //文件集合
        List<File> fileList = new ArrayList<>();
        for (int i = 0; i < insureFileList.size(); i++) {
            //原始保单文件url
            if (originalFlag){
                fileUrl = insureFileList.get(i);
                //保单文件owner
            }else {
                fileUrl = fileInfoService.selectUrlByOwner(insureFileList.get(i));
                if (StringUtils.isBlank(fileUrl)){
                    log.error("匹配投保数据处理，根据owner未查到文件url，owner：{}", insureFileList.get(i));
                    continue;
                }
            }
            //根据文件url下载文件到本地路径
            filePath = FileUtils.downloadUsingNIO(fileUrl, mergeBeforePath);
            file = new File(filePath);
            fileList.add(file);
        }
        //文件合并
        //合并后的文件名称
        String mergeAfterFileName = IdUtils.fastSimpleUUID() + ".pdf";
        //合并后的文件全路径
        String mergeAfterFullPath = mergeAfterPath + mergeAfterFileName;
        if (StringUtils.isNotEmpty(fileList)){
            //合并保单文件
            FileUtils.mulFilePdfToOne(fileList, mergeAfterFullPath);
            //file转换multipartFile
            MultipartFile multipartFile = new MockMultipartFile("file", mergeAfterFileName, "text/plain", Files.newInputStream(Paths.get(mergeAfterFullPath)));
            //将合并后的文件上传到oss并保存到数据库
            FileInfo fileInfo = fileInfoService.saveByOss(multipartFile, IdUtils.fastSimpleUUID(), true);
            if (fileInfo == null){
                log.error("匹配投保数据处理异常，保存文件到oss错误, 原始保单文件标识: {}", originalFlag);
                throw new RuntimeException("匹配投保数据处理异常，保存文件到oss错误");
            }
            //原始保单文件，返回文件url；保单文件，返回文件owner
            if (originalFlag){
                insureFile = fileInfo.getUrl();
            }else {
                //合并后的保单文件owner
                insureFile = fileInfo.getOwner();
            }
            //删除临时文件
            if (StringUtils.isNotBlank(basePath)){
                FileUtils.deleteDirectory(new File(basePath));
            }
        }else {
            log.info("匹配投保数据处理，未查到匹配后的保单文件，文件：{}", insureFileList);
        }
        return insureFile;
    }

    /**
     * @Description 匹配前保单和匹配后保单一对一  数据处理
     * @Author yangxm
     * @Date 2025/6/18 11:25
     * @param matchInsure   匹配投保数据
     * @param toInsureIdList    匹配后的保单id集合
     * @param fromInsureList    需要更新的保单信息
     * @param originalFlag  原始保单文件标识 true
     * @param dayFlag  保保单文件标识 true
     */
    private void oneToOneHandle(MatchInsure matchInsure, List<Long> toInsureIdList, List<Insure> fromInsureList, Boolean originalFlag, Boolean dayFlag) {
        log.info("匹配投保数据一对一处理，匹配前保单ID：{}, 匹配后保单ID：{} 原始保单文件标识: {}, 天保标识：{}", matchInsure.getFromInsureId(), toInsureIdList.get(0), originalFlag, dayFlag);
        //根据匹配后的保单id查询匹配后的保单信息
        Insure toInsure = insureMapper.selectToInsureByToInsureId(toInsureIdList.get(0));
        if (StringUtils.isNull(toInsure)) {
            log.info("匹配投保数据处理，未查到匹配后的保单信息，匹配前的保单ID:{}，匹配后的保单ID：{}, 原始保单文件标识: {}, 天保标识：{}", matchInsure.getFromInsureId(), toInsureIdList.get(0), originalFlag, dayFlag);
            return;
        }
        Insure fromInsure = new Insure();
        fromInsure.setId(matchInsure.getFromInsureId());
        if (originalFlag){
            fromInsure.setOriginalInsureFileUrl(toInsure.getOriginalInsureFileUrl());
        }else {
            fromInsure.setInsureNo(toInsure.getInsureNo());
            fromInsure.setInsureFile(toInsure.getInsureFile());
        }
        fromInsureList.add(fromInsure);
    }


}
